
#include "mouse_wheel_util.h"

#include <windowsx.h>

#include "base/auto_reset.h"

#include "hwnd_util.h"
#include "ui_base/view_prop.h"

namespace ui
{

    // Property used to indicate the HWND supports having mouse wheel messages
    // rerouted to it.
    static const char* const kHWNDSupportMouseWheelRerouting =
        "__HWND_MW_REROUTE_OK";

    static bool WindowSupportsRerouteMouseWheel(HWND window)
    {
        while(GetWindowLong(window, GWL_STYLE) & WS_CHILD)
        {
            if(!IsWindow(window))
            {
                break;
            }

            if(reinterpret_cast<bool>(
                ViewProp::GetValue(window, kHWNDSupportMouseWheelRerouting)))
            {
                return true;
            }
            window = GetParent(window);
        }
        return false;
    }

    static bool IsCompatibleWithMouseWheelRedirection(HWND window)
    {
        std::wstring class_name = GetClassName(window);
        // Mousewheel redirection to comboboxes is a surprising and
        // undesireable user behavior.
        return !(class_name==L"ComboBox" || class_name==L"ComboBoxEx32");
    }

    static bool CanRedirectMouseWheelFrom(HWND window)
    {
        std::wstring class_name = GetClassName(window);

        // Older Thinkpad mouse wheel drivers create a window under mouse wheel
        // pointer. Detect if we are dealing with this window. In this case we
        // don't need to do anything as the Thinkpad mouse driver will send
        // mouse wheel messages to the right window.
        if((class_name==L"Syn Visual Class") ||
            (class_name==L"SynTrackCursorWindowClass"))
        {
            return false;
        }

        return true;
    }

    ViewProp* SetWindowSupportsRerouteMouseWheel(HWND hwnd)
    {
        return new ViewProp(hwnd, kHWNDSupportMouseWheelRerouting,
            reinterpret_cast<HANDLE>(true));
    }

    bool RerouteMouseWheel(HWND window, WPARAM w_param, LPARAM l_param)
    {
        // Since this is called from a subclass for every window, we can get
        // here recursively. This will happen if, for example, a control
        // reflects wheel scroll messages to its parent. Bail out if we got
        // here recursively.
        static bool recursion_break = false;
        if(recursion_break)
        {
            return false;
        }
        // Check if this window's class has a bad interaction with rerouting.
        if(!IsCompatibleWithMouseWheelRedirection(window))
        {
            return false;
        }

        DWORD current_process = GetCurrentProcessId();
        POINT wheel_location = { GET_X_LPARAM(l_param), GET_Y_LPARAM(l_param) };
        HWND window_under_wheel = WindowFromPoint(wheel_location);

        if(!CanRedirectMouseWheelFrom(window_under_wheel))
        {
            return false;
        }

        // Find the lowest Chrome window in the hierarchy that can be the
        // target of mouse wheel redirection.
        while(window != window_under_wheel)
        {
            // If window_under_wheel is not a valid Chrome window, then return true to
            // suppress further processing of the message.
            if(!::IsWindow(window_under_wheel))
            {
                return true;
            }
            DWORD wheel_window_process = 0;
            GetWindowThreadProcessId(window_under_wheel, &wheel_window_process);
            if(current_process != wheel_window_process)
            {
                if(IsChild(window, window_under_wheel))
                {
                    // If this message is reflected from a child window in a different
                    // process (happens with out of process windowed plugins) then
                    // we don't want to reroute the wheel message.
                    return false;
                }
                else
                {
                    // The wheel is scrolling over an unrelated window. Make sure that we
                    // have marked that window as supporting mouse wheel rerouting.
                    // Otherwise, we cannot send random WM_MOUSEWHEEL messages to arbitrary
                    // windows. So just drop the message.
                    if(!WindowSupportsRerouteMouseWheel(window_under_wheel))
                    {
                        return true;
                    }
                }
            }

            // window_under_wheel is a Chrome window.  If allowed, redirect.
            if(IsCompatibleWithMouseWheelRedirection(window_under_wheel))
            {
                AutoReset<bool> auto_reset_recursion_break(&recursion_break, true);
                SendMessage(window_under_wheel, WM_MOUSEWHEEL, w_param, l_param);
                return true;
            }
            // If redirection is disallowed, try the parent.
            window_under_wheel = GetAncestor(window_under_wheel, GA_PARENT);
        }
        // If we traversed back to the starting point, we should process
        // this message normally; return false.
        return false;
    }

} //namespace ui